#include <assert.h>
#include <iostream>
#include "ExpertSystem.h"
#include "Tools.h"

const std::string	CExpertSystem::EQUAL		( "Equal" );
const std::string	CExpertSystem::IMPLICATE	( "Implicate" );
const std::string	CExpertSystem::AND			( "And" );
const std::string	CExpertSystem::OR			( "Or" );
const std::string	CExpertSystem::XOR			( "Xor" );
const std::string	CExpertSystem::NOT			( "Not" );
const std::string	CExpertSystem::INITIALFACT	( "InitialFact" );
const std::string	CExpertSystem::RULES		( "Rules" );
const std::string	CExpertSystem::SEARCH		( "Search" );

CExpertSystem::CExpertSystem( const std::string & _strNameFile ) :
m_ifInputFile( _strNameFile.c_str() )
{
	std::cout << "File : " << _strNameFile << std::endl << std::endl;
	assert( m_ifInputFile.is_open() );
}

CExpertSystem::~CExpertSystem( void )
{
	if ( m_ifInputFile.is_open() )
		m_ifInputFile.close();

	// Clearing rules
	this->m_mapRules.clear();

	// Clearing ndoes
	MapNodes::const_iterator itNodes = this->m_mapNodes.begin();
	MapNodes::const_iterator itNodesEnd = this->m_mapNodes.end();
	while (itNodes != itNodesEnd)
	{
		delete itNodes->second;
		++itNodes;
	}
	this->m_mapNodes.clear();

	// Clearing conditions
	ConditionsVector::const_iterator itCondition = this->m_vecConditions.begin();
	ConditionsVector::const_iterator itConditionEnd = this->m_vecConditions.end();
	while (itCondition != itConditionEnd)
	{
		delete *itCondition;
		++itCondition;
	}
	this->m_vecConditions.clear();
}

void				CExpertSystem::Init( void )
{
	std::ifstream	ifRuleFile( "Data\\Rules.txt" );

	assert( ifRuleFile.is_open() );

	while ( !ifRuleFile.eof() )
	{
		std::string	strKey, strValue;
		ifRuleFile >> strKey;
		ifRuleFile >> strValue;
		if ( strKey != "" && strValue != "" )
			m_mapRules[ strKey ] = strValue;
	}

	if ( ifRuleFile.is_open() )
		ifRuleFile.close();

}

void				CExpertSystem::Start( void )
{
	this->Init();
	this->ParseExpertSystemFile();
}

void				CExpertSystem::ParseExpertSystemFile( void )
{
	std::string	strBigWord;
	char		strBuffer[ 512 ];
	bool		bGotSearchingLine = false;

	while ( !m_ifInputFile.eof() )
	{
		m_ifInputFile.getline( strBuffer, 512 );
		strBigWord = strBuffer;

		if ( strBigWord == "" )
			continue;

		while ( strBigWord.find(" ") != std::string::npos )
		{
			strBigWord.erase( strBigWord.find(" "), 1 );
		}
		// At this point, the string is clean : no spaces are present in the string
		
		// If we have reached the "Search" line at the previous line, we search the nodes demanded
		if (bGotSearchingLine)
		{
			typedef std::map<char, bool>  MapResults;
			MapResults	results;

			std::string::const_iterator it = strBigWord.begin();
			std::string::const_iterator itEnd = strBigWord.end();
			while (it != itEnd)
			{
				char nodeId = *it++;
				results[nodeId] = this->SearchNodeValue(nodeId);
			}

			std::cout << "===================== RESULTS =======================" << std::endl;
			MapResults::const_iterator itResult = results.begin();
			MapResults::const_iterator itResultEnd = results.end();
			for (; itResult != itResultEnd; ++itResult)
				std::cout << itResult->first << " = " << itResult->second << std::endl;
			std::cout << std::endl;
			break;
		}

		// We can determine if the string is a "implication" or "equivalence"
		// If it contains the equal sign, this is an equivalence
		if (strBigWord.find(this->m_mapRules[CExpertSystem::EQUAL]) != std::string::npos)
		{
			this->HandleInitialFact(strBigWord);
		}
		// Otherwise, if it contains the "implies" sign, this is an implication
		else if (strBigWord.find(this->m_mapRules[CExpertSystem::IMPLICATE]) != std::string::npos)
		{
			this->HandleRule(strBigWord);
		}
		else if (strBigWord == CExpertSystem::SEARCH)
		{
			bGotSearchingLine = true;
		}
	}
}

/// Handls an initial fact
/// param str : Gets a string describing an equivalence
void				CExpertSystem::HandleInitialFact( const std::string& str )
{
	size_t pos = str.find(this->m_mapRules[CExpertSystem::EQUAL]);
	assert(pos != std::string::npos);

	if (str.size() < 3 || pos == 0 || pos == str.size() - 1)
	{
		std::cerr << "Initial fact : string malformed : '" << str << "'" << std::endl;
		return ;
	}

	std::string value = str.substr(pos + 1);
	if (value != "true" && value != "false" && value != "0" && value != "1")
	{
		std::cerr << "Initial fact : invalid value : '" << str << "'" << std::endl;
		return ;
	}

	bool bValue = value == "true" || value == "1" ;

	for (size_t i = 0; i < pos; ++i)
	{
		this->GetNodeById(str[i])->SetValue(bValue);
		std::cout << "Initial fact : " << str[i] << " = " << std::boolalpha << bValue << std::endl;
	}
}

// Splits a string with a given delimiter
typedef std::list<std::string>	StringList;
StringList		SplitString(const std::string& str, const std::string& delim)
{
	StringList	list;
	size_t posBegin = 0;
	for (size_t i = 0; i < str.size(); ++i)
	{
		if (str.find(delim, i) == i)
		{
			list.push_back(str.substr(posBegin, i - posBegin));
			posBegin = i + 1;
		}
	}
	list.push_back(str.substr(posBegin));
	return list;
}

// Finds the node in the list of the existing node
// Returns NULL if the nodeId is unknown
Node*				CExpertSystem::GetNodeById(char nodeId)
{
	MapNodes::const_iterator it = this->m_mapNodes.begin();
	MapNodes::const_iterator itEnd = this->m_mapNodes.end();
	for (; it != itEnd; ++it)
		if (it->first == nodeId)
			return it->second;
	Node* node = new Node(nodeId);
	this->m_mapNodes[nodeId] = node;
	return node;
}

/// Create a new ConditionSimple from a condition string such as "B" or "!A"
ConditionSimple*	CExpertSystem::CreateSimpleConditionFromString(const std::string& str, char& nodeIdCreated)
{
	// Check that the string's length is normal
	assert(str.size() == 1 || (str.size() == (1 + this->m_mapRules[CExpertSystem::NOT].size()) && str.find(this->m_mapRules[CExpertSystem::NOT]) == 0));

	// Get the letter and tell if there is is NOT condition (helped by the assert above)
	bool	bIsNot;
	if (str.size() == 1)
	{
		nodeIdCreated = str[0];
		bIsNot = false;
	}
	else
	{
		nodeIdCreated = str[1];
		bIsNot = true;
	}

	Node*	node = this->GetNodeById(nodeIdCreated);
	return new ConditionSimple(node, bIsNot);
}

/// Handles a rule
/// param str : Gets a string describing a rule
void				CExpertSystem::HandleRule( const std::string& str )
{
	size_t pos = str.find(this->m_mapRules[CExpertSystem::IMPLICATE]);
	assert(pos != std::string::npos);

	if (str.size() < 3 || pos == 0 || pos == str.size() - 1)
	{
		std::cerr << "Rules : string malformed : '" << str << "'"<< std::endl;
		return ;
	}

	std::string condition(str.substr(0, pos));
	std::string conclusion(str.substr(pos + this->m_mapRules[CExpertSystem::IMPLICATE].size()));

	typedef std::list<char> CharList;
	char nodeIdCreated;
	std::string createdSimpleConditionsForThisCondition;
	ICondition* mainCondition;
	// If there is no AND nor any OR, this is a simple condition
	if (condition.find(this->m_mapRules[CExpertSystem::AND]) == std::string::npos &&
		condition.find(this->m_mapRules[CExpertSystem::OR]) == std::string::npos)
	{
		mainCondition = this->CreateSimpleConditionFromString(condition, nodeIdCreated);
		createdSimpleConditionsForThisCondition += nodeIdCreated;
	}
	else
	{
		mainCondition = new ConditionAnd();
		// The order of priorities is : NOT OR AND
		StringList conditionByANDs = SplitString(condition, this->m_mapRules[CExpertSystem::AND]);
		StringList::const_iterator itAND = conditionByANDs.begin();
		StringList::const_iterator itANDEnd = conditionByANDs.end();
		for (; itAND != itANDEnd; ++itAND)
		{
			StringList conditionByORs = SplitString(*itAND, this->m_mapRules[CExpertSystem::OR]);
			StringList::const_iterator itOR = conditionByORs.begin();
			StringList::const_iterator itOREnd = conditionByORs.end();

			if (conditionByORs.size() < 2)
			{
				mainCondition->AddSimpleCondition(this->CreateSimpleConditionFromString(*itAND, nodeIdCreated));
				createdSimpleConditionsForThisCondition += nodeIdCreated;
			}
			else
			{
				ICondition* subOrCondition = new ConditionOr();
				for (; itOR != itOREnd; ++itOR)
				{
					subOrCondition->AddSimpleCondition(this->CreateSimpleConditionFromString(*itOR, nodeIdCreated));
					createdSimpleConditionsForThisCondition += nodeIdCreated;
				}
				mainCondition->AddSubCondition(subOrCondition);
			}
		}
	}

	// Now, we set the dependencies, according to the conclusion part of the implication
	// This should handle : 
	// a & c -> !b			a & b -> c & d			a & b -> !c & d
	// For each negation, we consider the opposite of the current condition found

	// First, we have to find the multiple conclusions:
	StringList conclusionByANDs = SplitString(conclusion, this->m_mapRules[CExpertSystem::AND]);
	StringList::const_iterator itAND = conclusionByANDs.begin();
	StringList::const_iterator itANDEnd = conclusionByANDs.end();
	bool bIsError;
	for (bIsError = false; !bIsError && itAND != itANDEnd; ++itAND)
	{
		char nodeIdConclusion;
		const std::string& ccl = *itAND;
		bool	bIsNot = false;

		// If we have a negation conclusion
		if (ccl.find(this->m_mapRules[CExpertSystem::NOT]) == 0)
		{
			bIsNot = true;
			nodeIdConclusion = ccl.substr(this->m_mapRules[CExpertSystem::NOT].size())[0];
		}
		else
			nodeIdConclusion = ccl[0];

		// We check that we are not making a loop : 
		std::string::const_iterator it = createdSimpleConditionsForThisCondition.begin();
		std::string::const_iterator itEnd = createdSimpleConditionsForThisCondition.end();
		while (it != itEnd)
		{
			const Node* node = this->GetNodeById(*it++);
			if (node->DependsOn(nodeIdConclusion) || createdSimpleConditionsForThisCondition.find(nodeIdConclusion) != std::string::npos)
			{
				bIsError = true;
				std::cerr << "An error occured : loop detected in condition" << std::endl;
				break;
			}
		}

		if (bIsError == false)
		{
			if (bIsNot)
				this->GetNodeById(nodeIdConclusion)->AddCondition(new ConditionNot(mainCondition));
			else
				this->GetNodeById(nodeIdConclusion)->AddCondition(mainCondition);
		}
	}

	this->m_vecConditions.push_back(mainCondition);
}


// Search a node : shows the steps to find the value
bool				CExpertSystem::SearchNodeValue( char nodeId )
{
	std::cout << "================== Searching node " << nodeId << " ==================" << std::endl;
	if (this->m_mapNodes.find(nodeId) == this->m_mapNodes.end())
	{
		std::cerr << "Node " << nodeId << "not found !!! " << std::endl;
		return false;
	}
	bool value = this->GetNodeById(nodeId)->GetBoolean();
	std::cout << "	Value = " << std::boolalpha << value << std::endl << std::endl;
	return value;
}
